﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using QuailServices.QuailDataServiceReference;

namespace Quail.DbLogger.Task
{
    public class QuailDbLoggerTask : ITaskImplementor, ITestRunId
    {
        private readonly string _taskName;
        private readonly DateTime _taskDate;
        private readonly QuailDbLoggerTask _parentTask;
        private readonly TestSession _session;
        private readonly List<DbLoggerTaskLogEntry> _logEntries = new List<DbLoggerTaskLogEntry>();
        private readonly List<QuailDbLoggerTask> _childTasks = new List<QuailDbLoggerTask>();
    	private volatile int _testRunId;
    	private long _testRunTaskId;

        /// <summary>
        /// Initializes a new instance of the <see cref="QuailDbLoggerTask"/> class.
        /// </summary>
        /// <param name="session">The test session.</param>
        /// <param name="libraryName">Name of the dll executing the test.</param>
        /// <param name="name">The root task name.</param>
        /// <param name="className">The classname of the test method (GetType().ToString())</param>
        /// <param name="environment">The environment.</param>
        public QuailDbLoggerTask(TestSession session, string libraryName, string name, string className, string environment)
        {
            _taskName = name;
            _session = session;
            _taskDate = DateTime.Now;
            ClassName = className;
            LibraryName = libraryName;
            Environment = environment;

            QuailDbLoggerThread.AddEntry(new InsertTestRun(
                                             session.SessionName,
                                             environment,
                                             libraryName,
                                             className,
                                             OnInsertTestRunComplete,
                                             _taskDate));
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="QuailDbLoggerTask"/> class.
        /// </summary>
        /// <param name="session">The test session.</param>
        /// <param name="parentTask">The parent task.</param>
        /// <param name="name">The name of the task.</param>
        public QuailDbLoggerTask(TestSession session, QuailDbLoggerTask parentTask, string name)
        {
            _parentTask = parentTask;
            _session = session;
            _taskName = name;
            _taskDate = DateTime.Now;

            SafeWaitTestRunId(
                testRunId =>
                parentTask.SafeWaitTestRunTaskId(
                    testRunTaskId =>
                    new InsertTestRunTask(
                        testRunTaskId,
                        testRunId,
                        name,
                        LogStatus.Pending,
                        OnInsertTestRunTaskComplete,
                        _taskDate)));
        }

        ~QuailDbLoggerTask()
        {
            Dispose(false);
        }

        /// <summary>
        /// Gets or sets a function to create the DashboardUrl
        /// </summary>
        public static Func<QuailDbLoggerTask, string> ToDashboardUrl
        {
            get; set;
        }

        /// <summary>
        /// Get sthe DashboardUrl for the logger entry
        /// </summary>
        public string AsDashboardUrl
        {
            get
            {
                if (ToDashboardUrl == null)
                    return null;

                return ToDashboardUrl(this);
            }
        }

        /// <summary>
        /// Gets or sets the ClassName
        /// </summary>
        public string ClassName { get; set; }

        /// <summary>
        /// Gets or sets the LibraryName
        /// </summary>
        public string LibraryName { get; set; }

        /// <summary>
        /// Gets or sets the Environment
        /// </summary>
        public string Environment { get; set; }


        /// <summary>
        /// Gets the task Name
        /// </summary>
        public string TaskName
        {
            get
            {
                return _taskName;
            }
        }

        /// <summary>
        /// Gets the TestRunId with a timeout for waiting
        /// </summary>
        /// <param name="ms"></param>
        /// <returns></returns>
        public int GetTestRunId(int ms)
        {
            if (_testRunId == 0)
            {
                var e = new ManualResetEvent(false);
                SafeWaitTestRunId(testid => e.Set());
                e.WaitOne(ms);
            }

            return _testRunId;
        }

        /// <summary>
        /// Gets the TestRunTaskId with a timeout for waiting
        /// </summary>
        /// <param name="ms"></param>
        /// <returns></returns>
        public long GetTestRunTaskId(int ms)
        {
            if (_testRunTaskId == 0)
            {
                var e = new ManualResetEvent(false);
                SafeWaitTestRunTaskId(testid => e.Set());
                e.WaitOne(ms);
            }

            return _testRunTaskId;
        }

        /// <summary>
        /// Gets the TestRunTaskId
        /// </summary>
        public long TestRunTaskId
        {
            get
            {
                return _testRunTaskId;
            }
        }

        /// <summary>
        /// Gets the FinishState for this task
        /// </summary>
        public LogStatus FinishState
        {
            get; private set;
        }

        /// <summary>
        /// Delegate for on complete events
        /// </summary>
        /// <param name="task">a reference to this task.</param>
        /// <param name="id">the new id.</param>
        public delegate void OnIntCompleteEvent(QuailDbLoggerTask task, int id);

		/// <summary>
		/// Delegate for on complete events
		/// </summary>
		/// <param name="task">a reference to this task.</param>
		/// <param name="id">the new id.</param>
		public delegate void OnLongCompleteEvent(QuailDbLoggerTask task, long id);

        private delegate IQuailDbLoggerEntry GenerateIQuailDbLoggerIntEntry(int id);
		private delegate IQuailDbLoggerEntry GenerateIQuailDbLoggerLongEntry(long id);

        private delegate void DoSomethingWithId(int id);
		private delegate void DoSomethingWithLongId(long id);

		private event OnIntCompleteEvent OnTestRunIdComplete;
		private event OnLongCompleteEvent OnTestRunTaskIdComplete;

        /// <summary>
        /// Returns an enumeration of all log entries from this task
        /// </summary>
        public IEnumerable<QuailDbLoggerTask> ChildTasks
        {
            get
            {
                lock (_childTasks)
                {
                    foreach (var ct in _childTasks)
                    {
                        yield return ct;
                    }
                }
            }
        }

        /// <summary>
        /// Returns an enumeration of all log entries from this task
        /// </summary>
        public IEnumerable<DbLoggerTaskLogEntry> LogEntries
        {
            get
            {
                lock (_logEntries)
                {
                    foreach (var le in _logEntries)
                        yield return le;
                }
            }
        }

        /// <summary>
        /// Returns a list of log entries from this task and all nested tasks matching a delegate
        /// </summary>
        /// <param name="predicate">A delegate that returns true when the desired logstatus is found</param>
        /// <returns>An enumeration of logger tasks</returns>
        public IEnumerable<DbLoggerTaskLogEntry> GetAllLogEntries(Func<DbLoggerTaskLogEntry, bool> predicate)
        {
            lock (_logEntries)
            {
                foreach (var le in _logEntries.Where(predicate))
                    yield return le;
            }

            lock (_childTasks)
            {
                foreach (var c in _childTasks)
                {
                    foreach (var le in c.GetAllLogEntries(predicate))
                        yield return le;
                }
            }
        }

        /// <summary>
        /// Returns the first child task with a given LogStatus value.
        /// </summary>
        /// <param name="statusmatch">The status to look for</param>
        /// <returns></returns>
        public string GetFirstTaskLogEntry(Func<LogStatus, bool> statusmatch)
        {
            DbLoggerTaskLogEntry le;

            lock (_logEntries)
            {
                le = _logEntries.Where(lle => statusmatch(lle.Status)).FirstOrDefault();
            }

            if (le != null && !string.IsNullOrEmpty(le.Message))
                return le.Message;

            lock (_childTasks)
                return _childTasks.Select(t => t.GetFirstTaskLogEntry(statusmatch)).Where(lem => !string.IsNullOrEmpty(lem)).FirstOrDefault();
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            // do nothing
        }

        /// <summary>
        /// Adds a message to the database
        /// </summary>
        /// <param name="state">the state of the message</param>
        /// <param name="message">the message</param>
        /// <param name="url">the url</param>
        public void AddImpl(LogStatus state, string message, string url)
        {
            DateTime now = DateTime.Now;

            lock (_logEntries)
                _logEntries.Add(new DbLoggerTaskLogEntry(state, message, now, url));

            SafeWaitTestRunTaskId(id =>
                                  new InsertTestRunLogEntry(
                                      id,
                                      state,
                                      url,
                                      message,
                                      now));
        }

        /// <summary>
        /// Begins a task
        /// </summary>
        /// <param name="session">The test session</param>
        /// <param name="taskname">The name of a task</param>
        /// <returns>A new Task object</returns>
        public ITaskImplementor BeginNewTaskImpl(TestSession session, string taskname)
        {
            var newtask = new QuailDbLoggerTask(session, this, taskname);

            lock (_childTasks)
                _childTasks.Add(newtask);

            return newtask;
        }

        /// <summary>
        /// Logs a screenshot
        /// </summary>
        /// <param name="state">The screenshot state</param>
        /// <param name="filename">Location of the screenshot image</param>
        /// <param name="message">The mesage for the screenshot</param>
        public void ScreenshotImpl(LogStatus state, string filename, string message)
        {
            // throw new NotImplementedException();
        }

        /// <summary>
        /// Impementation of the rollback
        /// </summary>
        public void RollbackImpl()
        {
            SafeWaitTestRunTaskId(id => new DeleteTestRunTask(id));
            lock (_parentTask._childTasks)
                _parentTask._childTasks.Remove(this);
        }

        /// <summary>
        /// Implementation of the add attribute
        /// </summary>
        /// <param name="attribute">name of attribute</param>
        /// <param name="message">message for attribute</param>
        public void AddAttributeImpl(string attribute, string message)
        {
            SafeWaitTestRunTaskId(id => new InsertTestRunTaskAttribute(id, attribute, message ?? string.Empty));
        }

        /// <summary>
        /// implementation of Finish
        /// </summary>
        /// <param name="state">the state of the task</param>
        public void FinishImpl(LogStatus state)
        {
            FinishState = state;

            DateTime now = DateTime.Now;
            SafeWaitTestRunTaskId(id => new FinishTestRunTask(id, state, now));

            if (state == LogStatus.Rollback && _parentTask != null)
            {
                lock (_parentTask._childTasks)
                    _parentTask._childTasks.Remove(this);
            }
        }

        /// <summary>
        /// Flushes the log
        /// </summary>
        public void FlushImpl()
        {
        }

        /// <summary>
        /// Stops the log
        /// </summary>
        /// <param name="logger">the test logger</param>
        public void StopImpl(TestLogger logger)
        {
            var state = logger.OverallState;
            FinishState = state;
            DateTime now = DateTime.Now;
            SafeWaitTestRunId(id => new FinishTestRun(id, state, now));
        }

        /// <summary>
        /// Stores the network traffic.
        /// </summary>
        /// <param name="url">the url or name for the traffic</param>
        /// <param name="remoteControlUrl">the url or name for the traffic remote host</param>
        /// <param name="urlCounter">the Url counter for the traffic</param>
        /// <param name="msUntilReady">Milliseconds until the page was ready (how long it took)</param>
        /// <param name="traffic">The traffic.</param>
        public void StoreNetworkTrafficImpl(string url, string remoteControlUrl, int urlCounter, int msUntilReady, List<NetworkTrafficData> traffic)
        {
            if (traffic != null && traffic.Count > 0)
            {
                SafeWaitTestRunId(id => new StoreNetworkTraffic(id, url, remoteControlUrl, urlCounter, msUntilReady, traffic));
            }
        }

        public void StoreMonitorImpl(LogStatus state, Enum scenario, string message)
        {
            DateTime now = DateTime.Now;
            SafeWaitTestRunId(id => new StoreMonitorLogEntry(id, state, scenario, message, now));
        }

		private void SafeWaitTestRunTaskId(GenerateIQuailDbLoggerLongEntry generate)
        {
            SafeWaitTestRunTaskId(id => QuailDbLoggerThread.AddEntry(generate(id)));
        }

		private void SafeWaitTestRunTaskId(DoSomethingWithLongId dosomething)
		{
			lock (this)
			{
				if (_testRunTaskId != 0)
					dosomething(_testRunTaskId);
				else
					OnTestRunTaskIdComplete += (obj, id) => dosomething(id);
			}
		}

        /// <summary>
        /// Executes the delegate when TestRunID is available.  If TestRunID is available "now" then 
        /// </summary>
        /// <param name="dosomething">the delegate to execute</param>
        private void SafeWaitTestRunId(DoSomethingWithId dosomething)
        {
            lock (this)
            {
                if (_testRunId != 0)
                    dosomething(_testRunId);
                else
                {
                    if (_parentTask != null)
                    {
                        _parentTask.SafeWaitTestRunId(testrunid =>
                        {
                            lock (this)
                            {
                                _testRunId = testrunid;
                            }

                            dosomething(testrunid);
                        });
                    }
                    else
                        OnTestRunIdComplete += (obj, testrunid) => dosomething(testrunid);
                }
            }
        }

        private void SafeWaitTestRunId(GenerateIQuailDbLoggerIntEntry generate)
        {
            SafeWaitTestRunId(id => QuailDbLoggerThread.AddEntry(generate(id)));
        }

        private void OnInsertTestRunComplete(int testrunid)
        {
            lock (this)
            {
                _testRunId = testrunid;
                QuailDbLoggerThread.AddEntry(new InsertTestRunTask(
                                                 null,
                                                 testrunid,
                                                 _taskName,
                                                 LogStatus.Pending,
                                                 OnInsertTestRunTaskComplete,
                                                 _taskDate));

                if (OnTestRunIdComplete != null)
                    OnTestRunIdComplete(this, _testRunId);
            }
        }

        private void OnInsertTestRunTaskComplete(long testrunruntaskid)
        {
            lock (this)
            {
                _testRunTaskId = testrunruntaskid;
                if (OnTestRunTaskIdComplete != null)
                    OnTestRunTaskIdComplete(this, _testRunTaskId);
            }
        }

        /// <summary>
        /// Class that contains the information in a log entry
        /// </summary>
        public class DbLoggerTaskLogEntry
        {
            ///<summary>
            /// Creates a new instance of a DbLoggerTaskEntry
            ///</summary>
            ///<param name="status">The status of a log entry</param>
            ///<param name="message">The message</param>
            ///<param name="date">The date</param>
            public DbLoggerTaskLogEntry(LogStatus status, string message, DateTime date, string url)
            {
                Status = status;
                Message = message;
                InsertDate = date;
                Url = url;
            }

            /// <summary>
            /// Gets the status of the log entry
            /// </summary>
            public LogStatus Status
            {
                get; private set;
            }

            /// <summary>
            /// Gets the message of the log entry
            /// </summary>
            public string Message
            {
                get; private set;
            }

            /// <summary>
            /// Gets the time the log entry was created
            /// </summary>
            public DateTime InsertDate { get; private set; }

            /// <summary>
            /// Gets or sets the Url of the log entry
            /// </summary>
            public string Url { get; private set; }
        }
    }
}
